package MonteCarlo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

public class Simulator {
	public double rate; //interest rate
	public double deltaT; //time interval
	public double volatility; //volatility of stock price 
	public double expected_payOff; // the expected pay off of the stock price
	public double simulated_payOff; //the simulated pay off of the stock price  
	private ArrayList payOffs = new ArrayList(); //keep all payoffs generated by Mento Carlo Simulation
	
	public Simulator(double rate, double deltaT, double volatility){
		this.rate = rate;
		this.deltaT = deltaT;
		this.volatility = volatility;
		this.expected_payOff = Math.pow( 1 + this.rate * this.deltaT, 360/(this.deltaT * 365));
	}
	
	
	/**construct deltaS/S sequences: deltaS/S = 
	 * rate * deltaT + volatility * randomNum + Math.sqrt(deltaT)
	 * 
	 * @param randNumSeq: the sequence of random numbers following normal distribution
	 * @return
	 */
	private ArrayList getDeltaSSeq(ArrayList randNumSeq){
		ArrayList deltaSSeq = new ArrayList();
		for(int j = 0 ; j < randNumSeq.size(); j ++){
			double randNum = (Double)randNumSeq.get(j);
			double deltaS = this.rate * this.deltaT +  this.volatility * randNum * Math.sqrt(this.deltaT);
			deltaSSeq.add(deltaS);
		}
		return deltaSSeq;
	}

	/**derive the evolution path for stock prices
	 * 
	 * @param PV_S: the present value of the stock price
	 * @param deltaSSeq: a sequence of deltaS/S
	 * @return
	 */
	private ArrayList getEvolutionPath(double PV_S, ArrayList deltaSSeq){
		ArrayList path = new ArrayList();
		path.add(PV_S); // the start of this evoluation is 1.0;
		for(int j = 1; j < deltaSSeq.size() + 1 ; j ++){ 
			double previousS = (Double)path.get(j-1);
			double deltaS  = (Double)deltaSSeq.get(j-1);
			double currentS = previousS * (1 + deltaS);
			path.add(currentS);
		}
		return path;
	}

	
	/**derive M payoffs by Monte-Carlo simulation(without antithetic), 
	 * each payoff is the last value of the N-lengthed path
	 * 
	 * @param PV_S: the present value of the stock price
	 * @param M: payoff numbers
	 * @param N: the simulated path length
	 * @return
	 */
	private ArrayList getSimulatedPayOffs_withoutAntithetic(double PV_S, int M, int N){
		ArrayList payOffs = new ArrayList();//keep all M pay offs simulated by Monte-Carlo approaches
		
		RandomNumGenerator gen = new RandomNumGenerator();
		for(int i = 0; i < M; i ++){
			//1. generate N normally distributed random number
			ArrayList randNumSeq = gen.genRandomNumSequence(N);
			
			//2. construct the N deltaS values: deltaS/S = rate * deltaT + volatility * randomNum + Math.sqrt(deltaT) 
			ArrayList deltaSSeq = this.getDeltaSSeq(randNumSeq);
			
			//3. derive the evolution path for this sequence
			ArrayList path = this.getEvolutionPath(PV_S, deltaSSeq);
			
			//4. the pay off of this simulation is the last S
			double payOff = (Double)path.get(path.size()-1);
			payOffs.add(payOff);
		}	
		
		return payOffs;
	}
	
	
	/**derive M payoffs by Monte-Carlo simulation(withAntithetic), 
	 * each payoff is the last value of the N-lengthed path
	 * 
	 * @param PV_S
	 * @param M: payoff numbers
	 * @param N: the simulated path length
	 * @return
	 */
	private ArrayList getSimulatedPayOffs_withAntithetic(double PV_S, int M, int N){
		ArrayList payOffs = new ArrayList();//keep all M payoff-payoff_antithetic pairs simulated by Monte-Carlo approaches
		RandomNumGenerator gen = new RandomNumGenerator();
		
		//1.determine the loop times  
		int loops = 0;
		if(M % 2 == 0)
			loops = M/2;
		else
			loops = M/2 + 1;
		
		for(int i = 0; i < loops; i ++){
			//1. generate N normally distributed random number
			ArrayList randNumSeq = gen.genRandomNumSequence(N);
			ArrayList randNumSeq_antithetic = new ArrayList();
			for(int j = 0; j < randNumSeq.size(); j ++){
				double randomNum = (Double)randNumSeq.get(j);
				randNumSeq_antithetic.add(-randomNum);
			}
			
			//2. construct the N deltaS values
			ArrayList deltaSSeq = this.getDeltaSSeq(randNumSeq);			
			ArrayList deltaSSeq_antithetic = this.getDeltaSSeq(randNumSeq_antithetic);
			
			//3. derive the evolution path for this sequence
			ArrayList path = this.getEvolutionPath(PV_S, deltaSSeq);
			ArrayList path_antithetic = this.getEvolutionPath(PV_S, deltaSSeq_antithetic);
			
			//4. determine the pay off of this simulation
			double payOff = (Double)path.get(path.size()-1);
			payOffs.add(payOff);
			
			double payOff_antithetic = (Double)path_antithetic.get(path_antithetic.size()-1);
			payOffs.add(payOff_antithetic);
		}	
		if(M % 2 != 0){
			//delete the last element
			payOffs.remove(payOffs.size()-1);
		}
			
		return payOffs;
	}
	
	
	/**The estimated value of the derivative is surrogated by the 
	 * average of the discounted pay off derived by Monte-Carlo simulation
	 * 
	 * @param PV_S: the present value of the stock price 
	 * @param M: trial times
	 * @param N: the length of simulated path
	 * @param withAntithetic
	 * @return
	 */
	public double getSimulatedPayOff(double PV_S, int M, int N, boolean withAntithetic){
		if(this.payOffs.size() == 0){
			if(withAntithetic){
				this.payOffs = this.getSimulatedPayOffs_withAntithetic(PV_S, M, N);
			}else{
				this.payOffs = this.getSimulatedPayOffs_withoutAntithetic(PV_S, M, N);
			}	
		}
		
		//the estimated value of this derivative is the averaged pay off of this simulation
		double sum = 0.0;
		for(int i = 0; i < payOffs.size(); i ++){
			sum += (Double)payOffs.get(i);
		}
		this.simulated_payOff = sum /(double)payOffs.size();
		
		return this.simulated_payOff;
	}
	
	/**get the mean, std of the simulated pay off, compare the diff between
	 * the expected and simulated final values, and estimate the error range of this simulation  
	 * 
	 * 
	 */
	public void analyzeSimulation(){		
		//1. get the average of discounted pay off to serve as 
		//the estimated future value of the derivative		
		double mean_payOff = this.simulated_payOff;
		
		//2. get the std of simulated pay offs 
		double std_payOffs = 0.0;
		for(int i = 0; i < payOffs.size(); i++ ){
			double payOff = (Double)payOffs.get(i);
			std_payOffs += Math.pow(payOff - mean_payOff, 2);
		}
		std_payOffs = Math.sqrt(std_payOffs/(payOffs.size()-1));
		
		//3. get the diff between the expected pay off and the simulated one
		double diff = mean_payOff - this.expected_payOff;
		
		//4. estimate the error ranges of this simulation
		ArrayList errorObjects = new ArrayList(); //keep the pairs of confidenceLevel-statisticValue
		errorObjects.add(new ErrorAnalysis(0.80, 1.282));
		errorObjects.add(new ErrorAnalysis(0.90, 1.645));
		errorObjects.add(new ErrorAnalysis(0.95, 1.960));
		errorObjects.add(new ErrorAnalysis(0.98, 2.326));
		errorObjects.add(new ErrorAnalysis(0.99, 2.576));
		errorObjects.add(new ErrorAnalysis(0.999, 3.291));
		
		for(int i = 0; i < errorObjects.size(); i ++){
			ErrorAnalysis error = (ErrorAnalysis)errorObjects.get(i);
			error.estimateErrors(mean_payOff, std_payOffs, payOffs.size());
		}
		
		//print out the analysis results
		StringBuilder sb = new StringBuilder();
		sb.append("simulated:" + mean_payOff +"\t" + "expected:" + this.expected_payOff + "\t"+ "diff(simulated-expected):" + diff + "\t" + "stdev:" + std_payOffs + "\n");
		for(int i = 0; i < errorObjects.size(); i ++){
			ErrorAnalysis error = (ErrorAnalysis)errorObjects.get(i);
			sb.append(error.toString() + "\n");
		}
		
		System.out.println(sb.toString());
	}
	
	public static void main(String[] args){
		double rate = 0.04;
		double deltaT = 0.082191781;
		double volatility = 0.23;
		double PV_S = 1.0; // the present stock value
		
		boolean withAntithetic = true;
		Simulator sim = new Simulator(rate, deltaT, volatility);
		int M = 8;
		int N = 12;
		
		//for testing purpose
//		withAntithetic = true;
//		sim.payOffs.add(1.21752147);
//		sim.payOffs.add(0.921829057);
//		sim.payOffs.add(1.112357229);
//		sim.payOffs.add(1.07088824);
//		sim.payOffs.add(1.012085323);
//		sim.payOffs.add(1.306662744);
//		sim.payOffs.add(1.00011499);
//		sim.payOffs.add(1.567684098);
		
//		withAntithetic = true;
//		sim.payOffs.add(1.21752147);
//		sim.payOffs.add(0.855574662);
//		sim.payOffs.add(0.921829057);
//		sim.payOffs.add(1.117888109);
//		sim.payOffs.add(1.112357229);
//		sim.payOffs.add(0.918060551);
//		sim.payOffs.add(1.07088824);
//		sim.payOffs.add(0.965745628);
		
		sim.getSimulatedPayOff(PV_S, M, N, withAntithetic);
		sim.analyzeSimulation();
	}
}
